/**
 * Created by guolei on 2017/4/19.
 */
/**
 * The `Matter.Vector` module contains methods for creating and manipulating vectors.
 * Vectors are the basis of all the geometry related operations in the engine.
 * A `Matter.Vector` object is of the form `{ x: 0, y: 0 }`.
 *
 * See the included usage [examples](https://github.com/liabru/matter-js/tree/master/examples).
 *
 * @class Vector
 */

// TODO: consider params for reusing vector objects

var Vector = {};
(function () {

  /**
   * Creates a new vector.
   * @method create
   * @param {number} x
   * @param {number} y
   * @return {vector} A new vector
   */
  Vector.create = function (x, y) {
    return {x: x || 0, y: y || 0};
  };

  /**
   * Returns a new vector with `x` and `y` copied from the given `vector`.
   * @method clone
   * @param {vector} vector
   * @return {vector} A new cloned vector
   */
  Vector.clone = function (vector) {
    return {x: vector.x, y: vector.y};
  };

  /**
   * Returns the magnitude (length) of a vector.
   * @method magnitude
   * @param {vector} vector
   * @return {number} The magnitude of the vector
   */
  Vector.magnitude = function (vector) {
    return Math.sqrt((vector.x * vector.x) + (vector.y * vector.y));
  };

  /**
   * Returns the magnitude (length) of a vector (therefore saving a `sqrt` operation).
   * @method magnitudeSquared
   * @param {vector} vector
   * @return {number} The squared magnitude of the vector
   */
  Vector.magnitudeSquared = function (vector) {
    return (vector.x * vector.x) + (vector.y * vector.y);
  };

  /**
   * Rotates the vector about (0, 0) by specified angle.
   * @method rotate
   * @param {vector} vector
   * @param {number} angle
   * @return {vector} A new vector rotated about (0, 0)
   */
  Vector.rotate = function (vector, angle) {
    var cos = Math.cos(angle), sin = Math.sin(angle);
    return {
      x: vector.x * cos - vector.y * sin,
      y: vector.x * sin + vector.y * cos
    };
  };

  /**
   * Rotates the vector about a specified point by specified angle.
   * @method rotateAbout
   * @param {vector} vector
   * @param {number} angle
   * @param {vector} point
   * @param {vector} [output]
   * @return {vector} A new vector rotated about the point
   */
  Vector.rotateAbout = function (vector, angle, point, output) {
    var cos = Math.cos(angle), sin = Math.sin(angle);
    if (!output) output = {};
    var x = point.x + ((vector.x - point.x) * cos - (vector.y - point.y) * sin);
    output.y = point.y + ((vector.x - point.x) * sin + (vector.y - point.y) * cos);
    output.x = x;
    return output;
  };

  /**
   * Normalises a vector (such that its magnitude is `1`).
   * @method normalise
   * @param {vector} vector
   * @return {vector} A new vector normalised
   */
  Vector.normalise = function (vector) {
    var magnitude = Vector.magnitude(vector);
    if (magnitude === 0)
      return {x: 0, y: 0};
    return {x: vector.x / magnitude, y: vector.y / magnitude};
  };

  /**
   * Returns the dot-product of two vectors.
   * @method dot
   * @param {vector} vectorA
   * @param {vector} vectorB
   * @return {number} The dot product of the two vectors
   */
  Vector.dot = function (vectorA, vectorB) {
    return (vectorA.x * vectorB.x) + (vectorA.y * vectorB.y);
  };

  /**
   * Returns the cross-product of two vectors.
   * @method cross
   * @param {vector} vectorA
   * @param {vector} vectorB
   * @return {number} The cross product of the two vectors
   */
  Vector.cross = function (vectorA, vectorB) {
    return (vectorA.x * vectorB.y) - (vectorA.y * vectorB.x);
  };

  /**
   * Returns the cross-product of three vectors.
   * @method cross3
   * @param {vector} vectorA
   * @param {vector} vectorB
   * @param {vector} vectorC
   * @return {number} The cross product of the three vectors
   */
  Vector.cross3 = function (vectorA, vectorB, vectorC) {
    return (vectorB.x - vectorA.x) * (vectorC.y - vectorA.y) - (vectorB.y - vectorA.y) * (vectorC.x - vectorA.x);
  };

  /**
   * Adds the two vectors.
   * @method add
   * @param {vector} vectorA
   * @param {vector} vectorB
   * @param {vector} [output]
   * @return {vector} A new vector of vectorA and vectorB added
   */
  Vector.add = function (vectorA, vectorB, output) {
    if (!output) output = {};
    output.x = vectorA.x + vectorB.x;
    output.y = vectorA.y + vectorB.y;
    return output;
  };

  /**
   * Subtracts the two vectors.
   * @method sub
   * @param {vector} vectorA
   * @param {vector} vectorB
   * @param {vector} [output]
   * @return {vector} A new vector of vectorA and vectorB subtracted
   */
  Vector.sub = function (vectorA, vectorB, output) {
    if (!output) output = {};
    output.x = vectorA.x - vectorB.x;
    output.y = vectorA.y - vectorB.y;
    return output;
  };

  /**
   * Multiplies a vector and a scalar.
   * @method mult
   * @param {vector} vector
   * @param {number} scalar
   * @return {vector} A new vector multiplied by scalar
   */
  Vector.mult = function (vector, scalar) {
    return {x: vector.x * scalar, y: vector.y * scalar};
  };

  /**
   * Divides a vector and a scalar.
   * @method div
   * @param {vector} vector
   * @param {number} scalar
   * @return {vector} A new vector divided by scalar
   */
  Vector.div = function (vector, scalar) {
    return {x: vector.x / scalar, y: vector.y / scalar};
  };

  /**
   * Returns the perpendicular vector. Set `negate` to true for the perpendicular in the opposite direction.
   * @method perp
   * @param {vector} vector
   * @param {bool} [negate=false]
   * @return {vector} The perpendicular vector
   */
  Vector.perp = function (vector, negate) {
    negate = negate === true ? -1 : 1;
    return {x: negate * -vector.y, y: negate * vector.x};
  };

  /**
   * Negates both components of a vector such that it points in the opposite direction.
   * @method neg
   * @param {vector} vector
   * @return {vector} The negated vector
   */
  Vector.neg = function (vector) {
    return {x: -vector.x, y: -vector.y};
  };

  /**
   * Returns the angle in radians between the two vectors relative to the x-axis.
   * @method angle
   * @param {vector} vectorA
   * @param {vector} vectorB
   * @return {number} The angle in radians
   */
  Vector.angle = function (vectorA, vectorB) {
    return Math.atan2(Vector.cross(vectorA, vectorB), Vector.dot(vectorA, vectorB));
  };

  /**
   * Temporary vector pool (not thread-safe).
   * @property _temp
   * @type {vector[]}
   * @private
   */
  Vector._temp = [
    Vector.create(), Vector.create(),
    Vector.create(), Vector.create(),
    Vector.create(), Vector.create()
  ];

})();

export default Vector;